/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package io.simple.core.handlers;

import com.esotericsoftware.reflectasm.MethodAccess;
import com.google.common.base.Preconditions;
import com.google.protobuf.Message;
import injections.InjectionAnalyzer;
import io.simple.annotations.HandlerMethod;
import io.simple.core.RequestContext;
import io.simple.core.chain.RequestHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

/**
 * @author Tom
 */
public class MultiHandlerHelper {

    private static final Logger LOGGER = LoggerFactory.getLogger(MultiHandlerHelper.class);

    public static List<RequestHandler> createHandlers(MultiHandler mh) {
        Preconditions.checkNotNull(mh);
        List<RequestHandler> res = new ArrayList<>();
        processHandlingMethods(mh, res);
        return res;
    }

    private static void processHandlingMethods(MultiHandler mh, List<RequestHandler> res) {
        List<Method> methods = InjectionAnalyzer.getMethodsWithAnnotation(mh.getClass(), HandlerMethod.class);
        for (Method m : methods) {
            if (checkGenericSignature(m)) {
                RequestHandler handler = createGenericRequestHandler(mh, m);
                res.add(handler);
            } else if (checkBothGPBSignature(m)) {
                RequestHandler handler = createBothGPBRequestHandler(mh, m);
                res.add(handler);
            } else if (checkInputGPBSignature(m)) {
                RequestHandler handler = createInputGPBRequestHandler(mh, m);
                res.add(handler);
            } else if (checkOutputGPBSignature(m)) {
                RequestHandler handler = createOutputGPBRequestHandler(mh, m);
                res.add(handler);
            } else {
                throw new IllegalArgumentException("Method return types / parameters are not OK! " + m);
            }
        }
    }

    /**
     * Check's method signature, must be
     * <p/>
     * public ByteBuffer methodName(RequestContext context, ByteBuffer
     * content) throws Exception;
     *
     * @param m
     */
    public static boolean checkGenericSignature(Method m) {
        if (m.getParameterTypes().length != 2) {
            LOGGER.warn("Expecting 2 parameters - RequestContext and ByteBuffer/GPB for " + m.getName() + ", also return type must be GPB / ByteBuffer");
        }
        boolean returnOk = ByteBuffer.class.isAssignableFrom(m.getReturnType());
        boolean arg0Ok = RequestContext.class.isAssignableFrom(m.getParameterTypes()[0]);
        boolean arg1Ok = ByteBuffer.class.isAssignableFrom(m.getParameterTypes()[1]);
        return returnOk && arg0Ok && arg1Ok;
    }

    /**
     * Check's method signature, must be
     * <p/>
     * public Message methodName(RequestContext context, Message inMessage)
     * throws Exception;
     *
     * @param m
     */
    public static boolean checkBothGPBSignature(Method m) {
        if (m.getParameterTypes().length != 2) {
            LOGGER.warn("Expecting 2 parameters - RequestContext and ByteBuffer/GPB for " + m.getName() + ", also return type must be GPB / ByteBuffer");
        }
        boolean returnOk = Message.class.isAssignableFrom(m.getReturnType());
        boolean arg0Ok = RequestContext.class.isAssignableFrom(m.getParameterTypes()[0]);
        boolean arg1Ok = Message.class.isAssignableFrom(m.getParameterTypes()[1]);
        return returnOk && arg0Ok && arg1Ok;
    }

    /**
     * Check's method signature, must be
     * <p/>
     * public ChannelBuffer methodName(RequestContext context, Message
     * inMessage) throws Exception;
     *
     * @param m
     */
    public static boolean checkInputGPBSignature(Method m) {
        if (m.getParameterTypes().length != 2) {
            LOGGER.warn("Expecting 2 parameters - RequestContext and ByteBuffer/GPB for " + m.getName() + ", also return type must be GPB / ByteBuffer");
        }
        boolean returnOk = ByteBuffer.class.isAssignableFrom(m.getReturnType());
        boolean arg0Ok = RequestContext.class.isAssignableFrom(m.getParameterTypes()[0]);
        boolean arg1Ok = Message.class.isAssignableFrom(m.getParameterTypes()[1]);
        return returnOk && arg0Ok && arg1Ok;
    }

    /**
     * Check's method signature, must be
     * <p/>
     * public Message methodName(RequestContext context, ChannelBuffer content)
     * throws Exception;
     *
     * @param m
     */
    public static boolean checkOutputGPBSignature(Method m) {
        if (m.getParameterTypes().length != 2) {
            LOGGER.warn("Expecting 2 parameters - RequestContext and ByteBuffer/GPB for " + m.getName() + ", also return type must be GPB / ByteBuffer");
        }
        boolean returnOk = Message.class.isAssignableFrom(m.getReturnType());
        boolean arg0Ok = RequestContext.class.isAssignableFrom(m.getParameterTypes()[0]);
        boolean arg1Ok = ByteBuffer.class.isAssignableFrom(m.getParameterTypes()[1]);
        return returnOk && arg0Ok && arg1Ok;
    }

    private static RequestHandler createGenericRequestHandler(final MultiHandler mh, final Method m) {
        HandlerMethod an = m.getAnnotation(HandlerMethod.class);
        final MethodAccess access = MethodAccess.get(mh.getClass());
        final String methodName = m.getName();
        final int nameIndex = access.getIndex(methodName);
        final String path = an.path();
        final int flags = an.flags();
        final Pattern pattern;
        if (flags != 0) {
            pattern = Pattern.compile(path, flags);
        } else {
            pattern = Pattern.compile(path);
        }
        RequestHandler handler = new RequestHandler() {
            @Override
            public ByteBuffer processRequest(RequestContext context, ByteBuffer data) throws Exception {
                Object result = access.invoke(mh, nameIndex, context, data);
                return (ByteBuffer) result;
            }

            @Override
            public Pattern getPath() {
                return pattern;
            }

            @Override
            public void close() throws IOException {
                // do nothing
            }
        };
        return handler;
    }

    private static RequestHandler createBothGPBRequestHandler(final MultiHandler mh, Method m) {
        HandlerMethod an = m.getAnnotation(HandlerMethod.class);
        final MethodAccess access = MethodAccess.get(mh.getClass());
        final String methodName = m.getName();
        final int nameIndex = access.getIndex(methodName);
        final String path = an.path();
        final int patternFlags = an.flags();
        Class<? extends Message> in = (Class<? extends Message>) m.getParameterTypes()[1];
        RequestHandler handler = new GPBJSONHandler(in) {
            @Override
            public Message process(RequestContext context, Message msg) throws Exception {
                return (Message) access.invoke(mh, nameIndex, context, msg);
            }

            @Override
            public Pattern getPath() {
                if (patternFlags == 0) {
                    return Pattern.compile(path);
                } else {
                    return Pattern.compile(path, patternFlags);
                }
            }

            @Override
            public void close() throws IOException {
                // do nothing
            }
        };
        return handler;
    }

    private static RequestHandler createInputGPBRequestHandler(final MultiHandler mh, Method m) {
        HandlerMethod an = m.getAnnotation(HandlerMethod.class);
        final MethodAccess access = MethodAccess.get(mh.getClass());
        final String methodName = m.getName();
        final int nameIndex = access.getIndex(methodName);
        final String path = an.path();
        final int patternFlags = an.flags();
        Class<? extends Message> in = (Class<? extends Message>) m.getParameterTypes()[1];
        RequestHandler handler = new GPBJSONIncomingHandler(in) {
            @Override
            public ByteBuffer process(RequestContext context, Message msg) throws Exception {
                return (ByteBuffer) access.invoke(mh, nameIndex, context, msg);
            }

            @Override
            public Pattern getPath() {
                if (patternFlags == 0) {
                    return Pattern.compile(path);
                } else {
                    return Pattern.compile(path, patternFlags);
                }
            }

            @Override
            public void close() throws IOException {
                // do nothing
            }
        };
        return handler;
    }

    private static RequestHandler createOutputGPBRequestHandler(final MultiHandler mh, Method m) {
        HandlerMethod an = m.getAnnotation(HandlerMethod.class);
        final MethodAccess access = MethodAccess.get(mh.getClass());
        final String methodName = m.getName();
        final int nameIndex = access.getIndex(methodName);
        final String path = an.path();
        final int patternFlags = an.flags();
        Class<? extends Message> in = (Class<? extends Message>) m.getParameterTypes()[1];
        RequestHandler handler = new GPBJSONOutgoingHandler() {

            @Override
            public Pattern getPath() {
                if (patternFlags == 0) {
                    return Pattern.compile(path);
                } else {
                    return Pattern.compile(path, patternFlags);
                }
            }

            @Override
            public void close() throws IOException {
                // do nothing
            }

            @Override
            public Message process(RequestContext context, ByteBuffer content) throws Exception {
                return (Message) access.invoke(mh, nameIndex, context, content);
            }
        };
        return handler;
    }
}
